今天來把 APP 畫面 串接 api,先來講一下流程好了。
Redux Toolkit 的 createAsyncThunk 來處理與後端的非同步請求。Slice 保存到 Redux Store 中,並管理各種狀態。在這邊定義昨天的 CRUD api 的路徑與方法
這些方法會通過之前寫的 Axios 來發送 HTTP 請求,並將結果返回給前端使用。
[!NOTE]
API_ENDPOINT是我們的基礎 API 路徑,並且透過prefix統一管理路徑的前綴。
import api from '../lib/configAxios';
import {API_ENDPOINT} from '../lib/configAxios';
import {ICreateProps, ICreateReportResponse} from '../types/chp555Report';
const prefix = 'chp555-report';
const chp555ReportApi = {
  createReport(data: ICreateProps) {
    return api.post<ICreateReportResponse>(`${API_ENDPOINT}/${prefix}`, data);
  },
  fetchAllReports() {
    return api.get(`${API_ENDPOINT}/${prefix}`);
  },
  fetchReportById(reportId: string) {
    return api.get(`${API_ENDPOINT}/${prefix}/${reportId}`);
  },
  updateReport(data: any) {
    return api.patch(`${API_ENDPOINT}/${prefix}/report`, data);
  },
};
export default chp555ReportApi;
Action 是我們與後端 API 溝通的中介,負責將 API 的結果轉化為 Redux 狀態變更的一部分。
在 createChp555Report 這個 action 中,我們傳入 data,這個 data 是用戶的資料,這邊設計會回傳 reportId 在後續的操作中使用。
export const createChp555Report = createAsyncThunk(
  CHP555_REPORT.CREART_CHP555_REPORT,
  async (data: ICreateProps, thunkAPI) => {
    try {
      const response = await chp555ReportApi.createReport(data);
      return response.data;
    } catch (error: unknown) {
      if (error instanceof Error) {
        return thunkAPI.rejectWithValue(error.message);
      }
      return thunkAPI.rejectWithValue('未知錯誤');
    }
  },
);
這裡的 fetchAllReports 則是負責調用 API 並將所有報告資料保存到 Redux 中,供後續的畫面顯示使用。
export const fetchAllReports = createAsyncThunk(
  CHP555_REPORT.FETCH_ALL_REPORTS,
  async (_, thunkAPI) => {
    try {
      const response = await chp555ReportApi.fetchAllReports();
      return response.data;
    } catch (error: unknown) {
      const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
      return thunkAPI.rejectWithValue(errorMessage);
    }
  },
);
updateReport 則是接收要更新的資料並發送到後端。當更新成功後,Redux 中的狀態會被更新,前端畫面也會同步反映最新的資料。
export const updateReport = createAsyncThunk(
  CHP555_REPORT.UPDATE_REPORT,
  async (data: any, thunkAPI) => {
    try {
      const response = await chp555ReportApi.updateReport(data);
      return response.data;
    } catch (error: unknown) {
      const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
      return thunkAPI.rejectWithValue(errorMessage);
    }
  },
);
透過 fetchReportById,我們可以根據報告的 ID 來取得特定報告的詳細資料,這在進入編輯頁面時非常有用。
export const fetchReportById = createAsyncThunk(
  CHP555_REPORT.FETCH_REPORT_BY_ID,
  async (reportId: string, thunkAPI) => {
    try {
      const response = await chp555ReportApi.fetchReportById(reportId);
      return response.data;
    } catch (error: unknown) {
      const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
      return thunkAPI.rejectWithValue(errorMessage);
    }
  },
);
接下來我們需要將取得的 API 資料存入到 Redux Store 中,方便在不同頁面可以使用相同資料。
在 initialState 中,我們定義了報告列表、單一報告、編輯中的報告 ID、選項資料、請求狀態以及錯誤訊息。這些資料會在我們串接 API 並接收到回應時被更新。
const initialState: Chp555ReportState = {
  reports: [],
  report: {},
  editingReportId: '',
  options: {},
  status: ReduxStateStatus.IDLE,
  error: null,
};
透過 createSlice,我們將各個 Action 的結果處理邏輯統一管理起來。每個 action 都會有 pending、fulfilled 和 rejected 狀態 (這邊我們另外寫types定義),分別對應到請求進行中、成功和失敗的狀況。這裡我們還會更新 status,讓 UI 可以根據不同狀態來顯示 Loading 畫面或錯誤提示。
const chp555ReportSlice = createSlice({
  name: 'chp555Reports',
  initialState,
  reducers: {},
  extraReducers: builder => {
    builder
      .addCase(createChp555Report.pending, state => {
        state.status = ReduxStateStatus.LOADING;
      })
      .addCase(createChp555Report.fulfilled, (state, action) => {
        state.status = ReduxStateStatus.SUCCEEDED;
        state.editingReportId = action.payload.reportId;
      })
      .addCase(createChp555Report.rejected, (state, action: any) => {
        state.status = ReduxStateStatus.FAILED;
        state.error = action.payload.message;
      })
      .addCase(fetchAllReports.pending, state => {
        state.status = ReduxStateStatus.LOADING;
      })
      .addCase(fetchAllReports.fulfilled, (state, action) => {
        state.status = ReduxStateStatus.SUCCEEDED;
        state.reports = action.payload;
      })
      .addCase(fetchReportOptions.pending, state => {
        state.status = ReduxStateStatus.LOADING;
      })
      .addCase(fetchReportOptions.fulfilled, (state, action) => {
        state.status = ReduxStateStatus.SUCCEEDED;
        state.options = action.payload;
      })
      .addCase(updateReport.pending, state => {
        state.status = ReduxStateStatus.LOADING;
      })
      .addCase(updateReport.fulfilled, (state, _action) => {
        state.status = ReduxStateStatus.SUCCEEDED;
      });
  },
});
export default chp555ReportSlice.reducer;
在首頁,我們提供了一個按鈕,讓使用者可以建立新的報告。當按下 Create Chp555Report 按鈕時,我們會觸發 createChp555Report action,並在成功後導向報告編輯頁面。這裡使用了 dispatch 方法來呼叫 action,並且利用 navigation 來進行頁面跳轉。
import {useDispatch, useSelector} from 'react-redux';
...
const HomePage: React.FC<HomePageProps> = ({navigation}) => {
const dispatch = useDispatch();
...
  const handleCreateReport = async () => {
    try {
      await dispatch(createChp555Report({userId, username})).then(() => {
        navigation.navigate(RouteNames.ReportPage);
      });
    } catch (error) {
      console.error('Error creating report:', error);
    }
  };
...
}

進入編輯頁面後,我們會根據報告 ID 來自動取得對應的報告資料。這裡我們設置了一個 loading 狀態,當資料還沒取得時,畫面會顯示 loading,避免使用者看到不完整的畫面。
...
const [isLoading, setIsLoading] = useState(true);
...
  useEffect(() => {
    if (reportId) {
      dispatch(fetchReportById({id: reportId}))
        .then(unwrapResult)
        .then(() => setIsLoading(false))
        .catch(error => {
          console.error('Failed to fetch report:', error);
          setIsLoading(false);
        });
    } else {
      setIsLoading(false);
    }
  }, [reportId]);
當使用者修改資料並提交後,我們會使用 updateReport action 來更新資料,並且在成功或失敗後,分別顯示對應的通知訊息。
  const handleSubmit = async () => {
    const data = {
      ...formState,
      id: reportId,
    };
    try {
      await dispatch(updateReport(data)).then(unwrapResult);
      toast.show({
        title: 'Success',
        description: 'Report updated successfully!',
        placement: 'top',
        backgroundColor: 'green.500',
      });
    } catch (error: any) {
      toast.show({
        title: 'Error',
        description: `Failed to update report: ${error.message}`,
        placement: 'top',
        backgroundColor: 'red.500',
      });
    }
  };

在報告列表頁面,我們會透過 fetchAllReports 取得所有的報告資料,並將其渲染到頁面上。每個報告都會有一個編輯按鈕,點擊後會跳轉到對應的報告編輯頁面,這樣使用者可以查看或修改該報告的詳細內容。
...
 useEffect(() => {
    dispatch(fetchAllReports());
  }, [dispatch]);
  // 取得所有报告
  const reports = useSelector(state => state.chp555Reports.reports);
  // 跳轉編輯
  const handleEdit = id => {
    navigation.navigate(RouteNames.ReportPage, {editReportId: id});
    console.log(`Edit item with id: ${id}`);
  };
  return (
		....
		  {reports.map((item, index) => (
				.....)}
		...
};
export default SimpleTable;

我們把 api 串接到畫面了,剩下的部分就是完善表單欄位的處理,這些細節將會是下一步的工作。
#it鐵人